# -*- coding: utf-8 -*-
"""
core/代码预处理/preprocessor.py

代码解析模块。
使用Clang解析C代码文件，提取函数、全局变量、结构体等，并保留注释。
"""

import os
import clang.cindex
from core.utils import logger, read_file_content, write_json_file, write_file_content

# --- Clang配置 ---
# 设置Clang库文件路径, 根据实际环境调整
# 在很多系统中，如果 libclang.so 或 libclang.dll 在系统路径或 рядом с python скриптом, то это не обязательно.
# clang.cindex.Config.set_library_file("/usr/lib/x86_64-linux-gnu/libclang-14.so.1") # 示例路径
# 或者尝试自动发现
try:
    if not clang.cindex.Config.loaded:
        # 尝试常见的so名称，根据你的libclang版本调整
        possible_libs = [
            "libclang.so", "libclang-14.so", "libclang-13.so", 
            "libclang-12.so", "libclang-11.so", "libclang-10.so",
            "libclang.dylib", # macOS
            "libclang.dll" # Windows
        ]
        lib_found = False
        for lib_name in possible_libs:
            try:
                clang.cindex.Config.set_library_file(lib_name)
                clang.cindex.Config.get_cindex_library() # 尝试加载
                logger.info(f"Clang library loaded successfully using {lib_name}")
                lib_found = True
                break
            except clang.cindex.LibclangError:
                continue
        if not lib_found:
            logger.warning("Could not automatically find libclang. Please ensure it is installed and set the path if necessary.")
            # 作为最后的尝试，不设置特定文件，让它自己找
            try:
                clang.cindex.Config.get_cindex_library()
                logger.info("Clang library loaded successfully using default search paths.")
            except clang.cindex.LibclangError as e:
                logger.error(f"Failed to load libclang using default search paths: {e}. Code parsing will likely fail.")
except Exception as e:
    logger.error(f"Error during libclang configuration: {e}")

def get_comment_from_node(node):
    """
    尝试从AST节点提取注释。
    Clang的注释提取比较有限，主要针对Doxygen风格的注释。
    """
    comment = ""
    if node.brief_comment:
        comment += node.brief_comment
    # raw_comment 包含更多信息，但可能需要进一步处理
    # if node.raw_comment:
    #     if comment:
    #         comment += "\n" + node.raw_comment.decode("utf-8", "ignore")
    #     else:
    #         comment = node.raw_comment.decode("utf-8", "ignore")
    
    # 尝试获取节点前的注释 (这是一个更通用的方法，但需要遍历token)
    # 此方法较为复杂，暂时简化处理
    return comment.strip() if comment else ""

def get_node_code(node, source_code_lines):
    """
    从源代码行中提取节点的完整代码文本。
    """
    if not node.extent or not node.extent.start or not node.extent.end:
        return ""
    start_line = node.extent.start.line -1 # 0-indexed
    end_line = node.extent.end.line -1
    start_col = node.extent.start.column -1
    end_col = node.extent.end.column -1

    if start_line < 0 or end_line < 0 or start_col < 0 or end_col < 0:
        return ""
    
    if start_line == end_line:
        return source_code_lines[start_line][start_col:end_col]
    else:
        code_lines = []
        code_lines.append(source_code_lines[start_line][start_col:])
        for i in range(start_line + 1, end_line):
            code_lines.append(source_code_lines[i])
        if end_line < len(source_code_lines): # 确保end_line在范围内
             code_lines.append(source_code_lines[end_line][:end_col])
        return "\n".join(code_lines)

def parse_c_file(file_path, project_root_for_includes=None):
    """
    解析单个C/H文件，提取代码片段及其元数据。

    参数:
        file_path (str): 要解析的C/H文件路径。
        project_root_for_includes (str, optional): 项目根目录，用于构建相对包含路径。

    返回:
        list: 包含代码片段信息的字典列表。
    """
    logger.info(f"开始解析文件: {file_path}")
    fragments = []
    
    # 读取文件内容，Clang需要UTF-8编码的字符串
    # utils.read_file_content 会尝试GBK -> UTF-8
    content_utf8 = read_file_content(file_path, default_encoding="utf-8")
    if content_utf8 is None:
        logger.error(f"无法读取或解码文件 {file_path}，跳过解析。")
        return fragments
    
    source_code_lines = content_utf8.splitlines()

    try:
        index = clang.cindex.Index.create()
        # 准备Clang参数，-x c告诉Clang这是C代码，-x c++ 用于C++
        # 对于.h文件，也通常按C或C++来解析
        lang_opt = "c"
        if file_path.endswith(".cpp") or file_path.endswith(".hpp") or file_path.endswith(".cc"):
            lang_opt = "c++"
        
        clang_args = [f"-x{lang_opt}"]
        if project_root_for_includes:
            clang_args.append(f"-I{project_root_for_includes}")
            # 可以添加更多通用的系统include路径，但这会使配置复杂化
            # clang_args.extend([
            #     "-I/usr/include", 
            #     "-I/usr/local/include"
            # ])

        # 使用 unsaved_files 将内存中的UTF-8内容传递给Clang
        tu = index.parse(file_path, args=clang_args, 
                           unsaved_files=[(file_path, content_utf8)],
                           options=clang.cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
                                   clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES # 可选，加快解析但丢失函数体
                          )

        if not tu:
            logger.error(f"Clang未能解析文件: {file_path}")
            return fragments

        for diag in tu.diagnostics:
            if diag.severity >= clang.cindex.Diagnostic.Warning:
                logger.warning(f"Clang诊断信息 ({file_path} L{diag.location.line}): {diag.spelling}")

        for node in tu.cursor.get_children():
            # 只处理在主文件中定义的节点 (忽略来自include的)
            if node.location.file and node.location.file.name == file_path:
                fragment = None
                node_kind_str = str(node.kind).split(".")[-1]
                
                # 提取函数
                if node.kind == clang.cindex.CursorKind.FUNCTION_DECL:
                    if node.is_definition(): # 确保是函数定义而不是声明
                        fragment = {
                            "file_path": file_path,
                            "type": "function",
                            "name": node.spelling,
                            "start_line": node.extent.start.line,
                            "end_line": node.extent.end.line,
                            "comment": get_comment_from_node(node),
                            "code": get_node_code(node, source_code_lines)
                        }
                
                # 提取全局变量 (需要判断是否在函数外)
                elif node.kind == clang.cindex.CursorKind.VAR_DECL and node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
                    fragment = {
                        "file_path": file_path,
                        "type": "global_variable",
                        "name": node.spelling,
                        "start_line": node.extent.start.line,
                        "end_line": node.extent.end.line,
                        "comment": get_comment_from_node(node),
                        "code": get_node_code(node, source_code_lines)
                    }

                # 提取结构体、联合、枚举定义
                elif node.kind in [clang.cindex.CursorKind.STRUCT_DECL, 
                                   clang.cindex.CursorKind.UNION_DECL, 
                                   clang.cindex.CursorKind.ENUM_DECL]:
                    if node.is_definition():
                        fragment = {
                            "file_path": file_path,
                            "type": node_kind_str.lower().replace("_decl", ""), # struct, union, enum
                            "name": node.spelling if node.spelling else "(anonymous)",
                            "start_line": node.extent.start.line,
                            "end_line": node.extent.end.line,
                            "comment": get_comment_from_node(node),
                            "code": get_node_code(node, source_code_lines)
                        }
                
                # 提取宏定义 (MacroDefinition)
                # Clang的Python绑定对宏的处理不如对其他C元素直接，可能需要更复杂的处理或不同的工具
                # 简单的宏定义可以通过遍历token或使用tu.get_tokens()来尝试捕获，但关联注释困难
                # 此处暂时不详细处理宏定义，可以后续增强

                if fragment:
                    # 过滤掉空的或过短的代码片段 (例如只有分号的声明)
                    if fragment["code"] and len(fragment["code"].strip()) > 1:
                        fragments.append(fragment)
                        logger.debug(f"提取到片段: {fragment["type"]} 	 {fragment["name"]} 	 L{fragment["start_line"]}-L{fragment["end_line"]}")
    
    except clang.cindex.LibclangError as e:
        logger.error(f"Clang库错误，解析文件 {file_path} 失败: {e}")
    except Exception as e:
        logger.error(f"解析文件 {file_path} 时发生未知错误: {e}")

    logger.info(f"文件 {file_path} 解析完成，提取到 {len(fragments)} 个代码片段。")
    return fragments

def preprocess_codebase(code_dir_path, output_json_path):
    """
    代码预处理模块的主函数。
    遍历指定目录下的所有C/H文件，解析它们，并将提取的代码片段保存到JSON文件。

    参数:
        code_dir_path (str): 包含C/H源代码文件的目录路径。
        output_json_path (str): 解析结果（代码片段列表）的输出JSON文件路径。

    返回:
        list: 所有解析出的代码片段列表，如果处理失败则返回空列表。
    """
    logger.info(f"开始预处理代码库: {code_dir_path}")
    all_fragments = []
    
    if not os.path.isdir(code_dir_path):
        logger.error(f"提供的代码库路径不是一个有效的目录: {code_dir_path}")
        return []

    for root, _, files in os.walk(code_dir_path):
        for file_name in files:
            if file_name.endswith(".c") or file_name.endswith(".h") or \ 
               file_name.endswith(".cpp") or file_name.endswith(".hpp"):
                file_path = os.path.join(root, file_name)
                # 使用code_dir_path作为include的根目录，Clang可以从中查找相对路径的头文件
                fragments_from_file = parse_c_file(file_path, project_root_for_includes=code_dir_path)
                all_fragments.extend(fragments_from_file)
    
    if all_fragments:
        if write_json_file(output_json_path, all_fragments):
            logger.info(f"所有代码片段已成功解析并保存到: {output_json_path}")
        else:
            logger.error(f"保存代码片段列表失败: {output_json_path}")
    else:
        logger.warning("未能从代码库中解析出任何代码片段。")
        # 即使没有片段，也创建一个空的json文件，以表示处理已完成
        write_json_file(output_json_path, []) 

    return all_fragments

if __name__ == "__main__":
    project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
    
    # 模拟的旧代码库路径 (在实际运行main.py时，这里会是用户提供的真实路径)
    # 为了独立测试，我们创建一个包含LED.c的模拟目录
    mock_code_base = os.path.join(project_root, "data", "input", "old_project_code_for_test")
    os.makedirs(mock_code_base, exist_ok=True)
    
    # 将用户提供的LED.c (假设已上传到 /home/ubuntu/upload/LED.c) 复制到模拟目录
    # 在真实环境中，文件会由主脚本放置到 data/input/old_project_code/
    # 此处为了测试，我们直接写入一个简化的LED.c内容
    led_c_content_for_test = """
    #include "LED.h"
    // 全局变量LED状态
    int g_led_status = 0;

    /**
     * @brief 初始化LED
     * 这是LED_Init函数的详细注释。
     */
    void LED_Init(void) {
        g_led_status = 1; // LED已初始化
    }

    // 控制LED亮度的函数
    void LED_SetBrightness(int level) {
        // 设置亮度代码
    }
    """
    led_h_content_for_test = """
    #ifndef LED_H
    #define LED_H
    // LED状态枚举
    typedef enum {
        LED_OFF = 0,
        LED_ON = 1
    } LED_State_E;
    void LED_Init(void);
    void LED_SetBrightness(int level);
    #endif
    """
    write_file_content(os.path.join(mock_code_base, "LED.c"), led_c_content_for_test)
    write_file_content(os.path.join(mock_code_base, "LED.h"), led_h_content_for_test)

    output_fragments_file = os.path.join(project_root, "data", "output", "parsed_code_fragments.json")
    os.makedirs(os.path.dirname(output_fragments_file), exist_ok=True)

    logger.info(f"代码预处理器独立测试开始，代码库: {mock_code_base}")
    parsed_fragments = preprocess_codebase(mock_code_base, output_fragments_file)

    if parsed_fragments:
        logger.info(f"代码预处理器测试成功完成。共提取 {len(parsed_fragments)} 个片段。")
        # for frag in parsed_fragments:
        #     logger.debug(f"  - {frag['type']}: {frag['name']} in {os.path.basename(frag['file_path'])}")
    else:
        logger.error("代码预处理器测试失败或未提取到片段。")


